package uem

import (
	"encoding/json"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"reflect"
	"strconv"
	"time"
)

type RedisContainer struct {
	pool    *redis.Pool
	index   int
	addr    string
	pwd     string
	max_con int
}

func NewRedisChannel(network string,
	addr string, pwd string, index int, max_con int) (*RedisContainer, error) {

	channel := newRedisContainer(network, addr, pwd, index, max_con)
	c := channel.GetConn()
	if c.Err() != nil {
		return nil, c.Err()
	}

	channel.index = index
	channel.addr = addr
	channel.pwd = pwd
	channel.max_con = max_con
	return channel, nil
}

func newRedisContainer(net, addr, pwd string, index, max_con int) (rd *RedisContainer) {

	rd = &RedisContainer{}
	rd.pool = &redis.Pool{
		MaxIdle:     max_con,
		MaxActive:   max_con,
		IdleTimeout: 1 * time.Second,
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
		Dial: func() (redis.Conn, error) {

			c, err := redis.Dial(net, addr)
			if err != nil {

				return nil, err
			}
			if pwd != "" {
				if _, err := c.Do("AUTH", pwd); err != nil {
					c.Close()
					return nil, err
				}
			}

			if _, err := c.Do("SELECT", index); err != nil {
				c.Close()
				return nil, err
			}

			return c, err
		},
	}

	return
}
func (c *RedisContainer) GetConn() redis.Conn {
	return c.pool.Get()
}
func (c *RedisContainer) Close() {
	c.pool.Close()
}
func Serialization(value interface{}) ([]byte, error) {

	if bytes, ok := value.([]byte); ok {
		return bytes, nil
	}
	switch v := reflect.ValueOf(value); v.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return []byte(strconv.FormatInt(v.Int(), 10)), nil
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return []byte(strconv.FormatUint(v.Uint(), 10)), nil
	case reflect.Map:
	}
	k, err := json.Marshal(value)
	return k, err
}
func Deserialization(byt []byte, ptr interface{}) (err error) {
	if bytes, ok := ptr.(*[]byte); ok {
		*bytes = byt
		return
	}
	if v := reflect.ValueOf(ptr); v.Kind() == reflect.Ptr {
		switch p := v.Elem(); p.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			var i int64
			i, err = strconv.ParseInt(string(byt), 10, 64)
			if err != nil {
				fmt.Printf("Deserialization: failed to parse int '%s': %s", string(byt), err)
			} else {
				p.SetInt(i)
			}
			return

		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			var i uint64
			i, err = strconv.ParseUint(string(byt), 10, 64)
			if err != nil {
				fmt.Printf("Deserialization: failed to parse uint '%s': %s", string(byt), err)
			} else {
				p.SetUint(i)
			}
			return
		}
	}
	err = json.Unmarshal(byt, &ptr)
	return
}
func (c *RedisContainer) SetString(name string, v interface{}) error {
	conn := c.pool.Get()
	defer conn.Close()
	_, err := conn.Do("SET", name, v)
	return err
}
func (c *RedisContainer) GetString(name string) (string, error) {

	conn := c.pool.Get()
	defer conn.Close()
	temp, err := redis.String(conn.Do("Get", name))
	return temp, err
}
func (c *RedisContainer) ExistIs(name string) (bool, error) {
	conn := c.pool.Get()
	defer conn.Close()
	v, err := redis.Bool(conn.Do("EXISTS", name))
	return v, err
}
func (c *RedisContainer) StringIncr(name string) (int, error) {
	conn := c.pool.Get()
	defer conn.Close()
	v, err := redis.Int(conn.Do("INCR", name))
	return v, err
}
func (c *RedisContainer) SetExpireTime(name string, newSecondsLifeTime int64) error {

	conn := c.pool.Get()
	defer conn.Close()
	_, err := conn.Do("EXPIRE", name, newSecondsLifeTime)
	return err
}
func (c *RedisContainer) Delete(keys ...interface{}) (bool, error) {
	conn := c.pool.Get()
	defer conn.Close()
	v, err := redis.Bool(conn.Do("DEL", keys...))
	return v, err
}
func (c *RedisContainer) StrKeyLen(name string) (int, error) {
	conn := c.pool.Get()
	defer conn.Close()
	v, err := redis.Int(conn.Do("STRLEN", name))
	return v, err
}
func (c *RedisContainer) Hashdel(name, key string) (bool, error) {
	conn := c.pool.Get()
	defer conn.Close()
	var err error
	v, err := redis.Bool(conn.Do("HDEL", name, key))
	return v, err
}
func (c *RedisContainer) ExistsHash(name, field string) (bool, error) {
	conn := c.pool.Get()
	defer conn.Close()
	var err error
	v, err := redis.Bool(conn.Do("HEXISTS", name, field))
	return v, err
}
func (c *RedisContainer) HashLen(name string) (int, error) {
	conn := c.pool.Get()
	defer conn.Close()
	v, err := redis.Int(conn.Do("HLEN", name))
	return v, err
}
func (c *RedisContainer) SetHM(name string, obj interface{}) (err error) {
	conn := c.pool.Get()
	defer conn.Close()
	_, err = conn.Do("HSET", redis.Args{}.Add(name).AddFlat(&obj)...)
	return
}
func (c *RedisContainer) GetHM(name string, fields ...string) ([]interface{}, error) {
	conn := c.pool.Get()
	defer conn.Close()
	args := []interface{}{name}
	for _, field := range fields {
		args = append(args, field)
	}
	value, err := redis.Values(conn.Do("HMGET", args...))

	return value, err
}
func (c *RedisContainer) SetH(name string, key string, value interface{}) (err error) {
	conn := c.pool.Get()
	defer conn.Close()
	v, _ := Serialization(value)
	_, err = conn.Do("HSET", name, key, v)
	return
}

func (c *RedisContainer) GetH(name, field string, v interface{}) (err error) {
	conn := c.pool.Get()
	defer conn.Close()
	temp, _ := redis.Bytes(conn.Do("Get", name, field))
	err = Deserialization(temp, &v) // 反序列化
	return
}
func (c *RedisContainer) Smembers(name string, v interface{}) (err error) {
	conn := c.pool.Get()
	defer conn.Close()
	temp, _ := redis.Bytes(conn.Do("smembers", name))
	err = Deserialization(temp, &v)
	return err
}
func (c *RedisContainer) ScardInt64s(name string) (int64, error) {
	conn := c.pool.Get()
	defer conn.Close()
	v, err := redis.Int64(conn.Do("SCARD", name))
	return v, err
}
func (c *RedisContainer) Push(name string, msg []byte) error {
	conn := c.pool.Get()
	defer conn.Close()
	_, err := conn.Do("publish", name, msg)
	return err
}
func (c *RedisContainer) ClearDB() error {

	conn := c.pool.Get()
	defer conn.Close()
	if _, err := conn.Do("SELECT", c.index); err != nil {
		return nil
	}
	if _, err := conn.Do("FLUSHDB"); err != nil {
		return err
	}
	return nil
}
func (c *RedisContainer) GetDBSize() int64 {

	conn := c.pool.Get()
	defer conn.Close()
	if _, err := conn.Do("SELECT", c.index); err != nil {
		return 0
	}
	v, err := redis.Int64(conn.Do("Dbsize"))
	if err != nil {
		return 0
	}
	return v
}
